package com.liuqing.www.java基础知识.网络通信;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
 * serversocket.accept以及socket.getInputStream都是会阻塞的
 * ，在服务器多线程编程中每个线程都会占用1M的内存，存在大量的阻塞状态会占用大量的系统资源
 * 恶意攻击甚至会导致系统崩溃，故需要统一管理，采取监听事件的方式，若有角色需要连接则开辟一个线程去处理
 * ByteBuffer
 * 		缓冲区的容量 是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。 
		缓冲区的限制 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负，并且不能大于其容量。 
		缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置不能为负，并且不能大于其限制。 
clear() 使缓冲区为一系列新的通道读取或相对放置 操作做好准备：它将限制设置为容量大小，将位置设置为 0。 
flip() 使缓冲区为一系列新的通道写入或相对获取 操作做好准备：它将限制设置为当前位置，然后将位置设置为 0。 
rewind() 使缓冲区为重新读取已包含的数据做好准备：它使限制保持不变，将位置设置为 0。 
flip。首先将限制设置为当前位置，然后将位置设置为 0。如果已定义了标记，则丢弃该标记。 \
reset将此缓冲区的位置重置为以前标记的位置。 
mark()在此缓冲区的位置设置标记。
分槽从从3到6
buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice(); 
allocate和allocateDirect方法都做了相同的工作，不同的是allocateDirect方法直接使用操作系统来分配Buffer。因而它将提供更快的访问速度。不幸的是，并非所有的虚拟机都支持这种直接分配的方法。
Sun推荐将以字节为单位的直接型缓冲区allocateDirect用于与大型文件相关并具有较长生命周期的缓冲区。
 直接字节缓冲区可以通过调用此类的 allocateDirect 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外，因此，它们对应用程序的内存需求量造成的影响可能并不明显。所以，建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下，最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
 * @author 韩世超
 *
 */
public class TCP信息传输异步IO {
	public static void main(String[] args) {
		new mserver().start();
		new mclient().start();
	}
}
class mserver extends Thread{
		/*****创建选择器，选择监听什么类型的事件 **/
	public void run(){
	try {
			System.out.println();
			Selector selector = Selector.open();
			// 打开服务器套接字通道
			ServerSocketChannel ssc1 = ServerSocketChannel.open();
			// 设置此通道为非阻塞模式
			ssc1.configureBlocking(false);
			// 从通道中获取一条serversocket
			ServerSocket ss = ssc1.socket();
			/**绑定端口与主机**/
			InetSocketAddress address = new InetSocketAddress("127.0.0.1",8000);
			ss.bind(address);
			/***向给定的选择器注册此通道的接受连接事件*/
			ssc1.register(selector, SelectionKey.OP_ACCEPT);
			ByteBuffer echoBuffer=ByteBuffer.allocate(500);
//			echoBuffer=echoBuffer.asReadOnlyBuffer();//转化为只读
			System.out.println("服务器启动成功");
			while (true) {
				// 这个方法会阻塞，直到至少有一个已注册的事件发生。
				// 当一个或者更多的事件发生时，此方法将返回所发生的事件的数量。
				/***此方法会阻塞，当1个也没触发。返回通道触发的数目***/
				selector.select();
				// 迭代所有的选择键，以处理特定的I/O事件。
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				Iterator<SelectionKey> iter = selectionKeys.iterator();
				SocketChannel sc;
				while (iter.hasNext()) {
					SelectionKey key = iter.next();
					/******readyOps()获取此键的 ready 操作集合。 ****/
					if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
						// 接受服务器套接字撒很能够传入的新的连接，并处理接受连接事件。
						System.out.println("监听到客户端连接信息");
						ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
						sc = ssc.accept();
						// 将新连接的套接字通道设置为非阻塞模式
						sc.configureBlocking(false);
						// 接受连接后，在此通道上从新注册读取事件，以便接收数据。
						sc.register(selector, SelectionKey.OP_READ);
						// 删除处理过的选择键
						iter.remove();
					} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
						// 处理读取事件，读取套接字通道中发来的数据。
						System.out.println("监听到客户端发来消息");
						sc = (SocketChannel) key.channel();
						// 读取数据
						while (true) {							
							echoBuffer.clear();
//							System.out.println(echoBuffer.remaining());
							int r = sc.read(echoBuffer);
							if (r == -1) {
								System.out.println("错误");
								break;
							}
							if (r >=2) {
								byte[] b=new byte[echoBuffer.position()];
								for(int i=0;i<echoBuffer.position();i++){
									b[i]=echoBuffer.get(i);
								}
								System.out.println(new String(b,"utf8"));
							}
//							echoBuffer.flip();
//							sc.write(echoBuffer);
//							bytesEchoed += r;
//							System.out.println(echoBuffer.position());
						}
//						sc.close();
							// 删除处理过的选择键
						iter.remove();
					}
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
}
	
}
class mclient extends Thread{
	public void run(){
		try {
				Socket s=new Socket("127.0.0.1",8000);
				OutputStream out=s.getOutputStream();
			BufferedOutputStream dot=new BufferedOutputStream(out);
				s.setKeepAlive(true);
				int i=0;
				while(true){
					try {
						if(out==null){
							out=s.getOutputStream();
						}
//					dot.writeUTF("&&&&&&&a\n");
//				
						i++;
						byte[] b=(i+"xxx是是").getBytes("utf8");
//						for(byte lb:b){
//						System.out.print(",,,"+lb);
//						}
//						System.out.println();
						dot.write(b);
					dot.flush();
					out.flush();
//					out.close();
//					dot.close();
					Thread.sleep(2000);
					"ss".intern();
					}catch(Exception e){
						e.printStackTrace();
					break;
					}
				}
//			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}